#include "fs/fatfs/fatfs.h"
#include "fs/fs.h"
#include "fs/file.h"
#include "dev/dev.h"
#include "tools/log.h"
#include "core/memory.h"
#include "tools/klib.h"
#include <sys/fcntl.h>

file_type_t diritem_get_type(diritem_t * item);
static int buff_read_sector(fat_t * fat, int sector);
static void name_to_diritem_name(const char * src, char * dest);
static int buff_write_sector(fat_t * fat, int sector);
int is_cluster_valid(cluster_t clus);
cluster_t cluster_get_next(fat_t * fat, cluster_t curr);
void cluster_free_chain(fat_t * fat, cluster_t start_cluster);
int cluster_set_next(fat_t * fat, cluster_t clus, int next_index);

// 分配簇号
cluster_t cluster_alloc(fat_t * fat, int count)
{
    cluster_t pre, curr, start;
    int c_total = fat->table_sectors * fat->bytes_per_sector / sizeof(cluster_t);
    int cnt = count;
    pre = start = FAT_CLUSTER_INVALID;
    for (curr = 2; (curr< c_total) && cnt; curr++) {
        cluster_t free = cluster_get_next(fat, curr);
        if (free == CLUSTER_NEXT_FREE) {
            // 记录首个簇
            if (!is_cluster_valid(start)) {
                start = curr;
            } 
        
            // 前一簇如果有效，则设置。否则忽略掉
            if (is_cluster_valid(pre)) {
                // 找到空表项，设置前一表项的链接
                int err = cluster_set_next(fat, pre, curr);
                if (err < 0) {
                    cluster_free_chain(fat, start);
                    return FAT_CLUSTER_INVALID;
                }
            }

            pre = curr;
            cnt--;
        }
    }

    // 最后的结点
    if (cnt == 0) {
        int err = cluster_set_next(fat, pre, FAT_CLUSTER_INVALID);
        if (err == 0) {
            return start;
        }
    }

    // 失败，空间不够等问题
    cluster_free_chain(fat, start);
    return FAT_CLUSTER_INVALID;
}

int expand_file(file_t * file, int inc_size)
{
    fat_t * fat = (fat_t *)file->fs->data;
    int inc_bytes = inc_size;
    int cluster_cnt;
    if ((file->size == 0) || (file->size % fat->bytes_per_clus == 0)) {
        // 文件为空，或者刚好达到的簇的末尾
        cluster_cnt = align_bound_up(inc_bytes, fat->bytes_per_clus) / fat->bytes_per_clus; 
    } else {
        // 文件非空，当前簇的空闲量，如果空间够增长，则直接退出了
        // 例如：大小为2048，再扩充1024,簇大小为1024
        int cfree = fat->bytes_per_clus - (file->size % fat->bytes_per_clus);
        if (cfree > inc_bytes) {
            return 0;
        }

        cluster_cnt = align_bound_up(inc_bytes - cfree, fat->bytes_per_clus) / fat->bytes_per_clus;
        if (cluster_cnt == 0) {
            cluster_cnt = 1;
        }
    }

    cluster_t start = cluster_alloc(fat, cluster_cnt);
    if (!is_cluster_valid(start)) {
        log_printf("no cluster for file write");
        return -1;
    }

    // 在文件关闭时，回写
    if (!is_cluster_valid(file->start_clus)) {
        file->curr_clus = file->start_clus = start;
    } else {
        // 建立链接关系
        int err = cluster_set_next(fat, file->curr_clus, start);
        if (err < 0) {
            return -1;
        }
    }

    return 0;
}

int cluster_set_next(fat_t * fat, cluster_t clus, int next_index)
{
    if(!is_cluster_valid(clus)) return -1;
    uint32_t offset = clus * sizeof(cluster_t);     // 簇号在FAT表中的字节位置
    uint32_t sector_index = offset / fat->bytes_per_sector;
    uint32_t sector_offset = offset % fat->bytes_per_sector;

    if(sector_index >= fat->table_sectors)
    {
        log_printf("cluster too big: %d", sector_index);
        return FAT_CLUSTER_INVALID;
    }

    int err = buff_read_sector(fat, fat->table_start + sector_index);
    if(err < 0) return -1;

    *((cluster_t *)(fat->fat_buffer + sector_offset)) = next_index;

    // 2个fat表都要更新
    for(int i=0;i<fat->table_count;i++)
    {
        int err = buff_write_sector(fat, fat->table_start + sector_index); 
        if(err < 0)
        {
            log_printf("write fat table failed: %d", sector_index);
            return -1;
        }
        sector_index += fat->table_sectors;
    }
    return 0;

}

void cluster_free_chain(fat_t * fat, cluster_t start_cluster)
{
    cluster_t curr = start_cluster;
    while(is_cluster_valid(curr))
    {   
        cluster_t next = cluster_get_next(fat, start_cluster);
        cluster_set_next(fat, curr, CLUSTER_NEXT_FREE);
        curr = next;
    }       
}

static int buff_write_sector(fat_t * fat, int sector)
{
    int cnt = dev_write(fat->fs->dev_id, sector, fat->fat_buffer, 1);
    return (cnt == 1) ? 0 : -1;
}

int write_dir_entry(fat_t * fat, diritem_t * item, int index)
{
    if(index < 0 || index >= fat->root_ent_count) return -1;

    int offset_bytes = index * sizeof(diritem_t);
    int sector = fat->root_start + offset_bytes / fat->bytes_per_sector;
    int err = buff_read_sector(fat, sector);
    if(err < 0) return err;

    diritem_t * to = (diritem_t *)(fat->fat_buffer + offset_bytes % fat->bytes_per_sector);
    kernel_memcpy(to, item, sizeof(diritem_t));
    return buff_write_sector(fat, sector);
}

int diritem_init(diritem_t * item, uint8_t attr, const char * name)
{
    name_to_diritem_name(name, item->DIR_Name);
    item->DIR_FstClusHI = (uint16_t)(FAT_CLUSTER_INVALID >> 16);
    item->DIR_FstClusL0 = (uint16_t)(FAT_CLUSTER_INVALID);
    item->DIR_FileSize = 0;
    item->DIR_Attr = attr;
    item->DIR_NTRes = 0;
    item->DIR_WrtDate = item->DIR_WrtTime = 0;
    item->DIR_CrtDate = item->DIR_CrtTime = 0;
    item->DIR_LastAccDate = 0;
}

int is_cluster_valid(cluster_t clus)
{
    return (clus>=2) && (clus<FAT_CLUSTER_INVALID); 
}

// 获取下一个簇号
cluster_t cluster_get_next(fat_t * fat, cluster_t curr)
{
    if(!is_cluster_valid(curr)) return FAT_CLUSTER_INVALID;
    uint32_t offset = curr * sizeof(cluster_t);     // 簇号在FAT表中的字节位置
    uint32_t sector_index = offset / fat->bytes_per_sector;
    uint32_t sector_offset = offset % fat->bytes_per_sector;

    if(sector_index >= fat->table_sectors)
    {
        log_printf("cluster too big: %d", sector_index);
        return FAT_CLUSTER_INVALID;
    }

    int err = buff_read_sector(fat, fat->table_start + sector_index);
    if(err < 0) return FAT_CLUSTER_INVALID;

    return *((cluster_t *)(fat->fat_buffer + sector_offset));
}

// 移动文件指针
static int move_file_pos(file_t * file, fat_t * fat, int move_bytes, int is_expand)
{
    uint32_t offset = file->pos % fat->bytes_per_clus;
    if(offset + move_bytes >= fat->bytes_per_clus)
    {
        cluster_t next = cluster_get_next(fat, file->curr_clus);
        if(next == FAT_CLUSTER_INVALID && is_expand)
        {
            int err = expand_file(file, fat->bytes_per_clus);
            if(err < 0) return -1;
            next = cluster_get_next(fat, file->curr_clus);
        }
        file->curr_clus =next;
    }

    file->pos += move_bytes;
    return 0;
}

static void name_to_diritem_name(const char * src, char * dest)
{
    kernel_memset(dest, ' ', 11);
    char * curr = dest, *end = dest+11;
    while(*src && curr<end)
    {
        char c = *src++;
        if(c <= 'z' && c>= 'a') c = c-'a'+'A';
        if(c == '.')
        {
            curr = dest + 8;
            continue;
        } 
        *curr++ = c;
    }
}

static int diritem_name_match(diritem_t * item, const char * name)
{
    char buff[11];
    name_to_diritem_name(name, buff);
    return (kernel_memcmp(buff, item->DIR_Name, 11) == 0);
}

static void read_from_diritem(fat_t * fat, file_t * file, diritem_t * item, int index)
{
    file->type = diritem_get_type(item);
    file->size = item->DIR_FileSize;
    file->pos = 0;
    file->file_item_index = index;
    file->start_clus = (item->DIR_FstClusHI << 16) | (item->DIR_FstClusL0);
    file->curr_clus = file->start_clus;
}

// fat文件系统 file.c <--> file____c__ 
static void diritem_get_name(diritem_t * item, char * name)
{ 
    char * c = name;
    char * ext = 0;         // 记录扩展名开始的位置
    kernel_memset(name, 0, 12);
    for(int i=0;i<11; i++)
    {
        if(item->DIR_Name[i] != ' ')
        {
            *c++ = item->DIR_Name[i];
        }
        if(i==7)
        {
            ext = c;
            *c++ = '.';
        } 
    }

    if(ext && ext[1] == '\0')       // 没有扩展名
    {
        ext[0] = '\0';
    }
}

static int buff_read_sector(fat_t * fat, int sector)
{
    if (sector == fat->curr_sector) {
        return 0;
    }

    int cnt = dev_read(fat->fs->dev_id, sector, fat->fat_buffer, 1);
    if (cnt == 1) {
        fat->curr_sector = sector;
        return 0;
    }
    return -1;
}

static diritem_t * read_dir_entry(fat_t * fat, int index)
{
    if(index < 0 || index >= fat->root_ent_count) return 0;

    int offset_bytes = index * sizeof(diritem_t);
    int sector = fat->root_start + offset_bytes / fat->bytes_per_sector;
    int err = buff_read_sector(fat, sector);
    if(err < 0) return 0;

    return (diritem_t *)(fat->fat_buffer + offset_bytes % fat->bytes_per_sector);
}

file_type_t diritem_get_type(diritem_t * item)
{
    file_type_t type = FILE_UNKNOWN;
    if(item->DIR_Attr & (DIRITEM_ATTR_HIDDEN | DIRITEM_ATTR_SYSTEM | DIRITEM_ATTR_VOLUME_ID))
    {
        return type;
    }

    if((item->DIR_Attr & DIRITEM_LONG_NAME) == DIRITEM_LONG_NAME)
    {
        return type;
    }

    return (item->DIR_Attr & DIRITEM_ATTR_DIRECTORY) ? FILE_DIR : FILE_NORMAL;
}

static int fatfs_mount(struct _fs_t * fs, int major, int minor)
{
    int dev_id = dev_open(major, minor, (void *)0);
    if(dev_id < 0)
    {
        log_printf("open disk failed. major: %d, minor: %d", major, minor);
        return -1;
    }

    dbr_t * dbr = (dbr_t *)memory_alloc_page();
    if(!dbr)
    {
        log_printf("mount failed: can not alloc page");
        goto mount_failed;
    }

    // disk_read(device_t * dev, int relative_sector, char * buff, int sector_count)
    int read_sector_count = dev_read(dev_id, 0, (char *)dbr, 1);
    if(read_sector_count != 1)
    {
        log_printf("read dbr failed.");
        goto mount_failed;
    }

    fat_t * fat = &(fs->fat_data);
    fat->bytes_per_sector = dbr->BPB_BytsPerSec;
    fat->sector_per_clus = dbr->BPB_SecPerClus;
    fat->bytes_per_clus = fat->sector_per_clus * fat->bytes_per_sector;
    fat->table_start = dbr->BPB_RsvdSecCnt;
    fat->table_sectors = dbr->BPB_FATSz16;
    fat->table_count = dbr->BPB_NumFATs;
    fat->root_start = fat->table_start + fat->table_count * fat->table_sectors;
    fat->root_ent_count = dbr->BPB_RootEntCnt;
    fat->data_start = fat->root_start + fat->root_ent_count * 32 / fat->bytes_per_sector;
    fat->fs = fs;
    fat->fat_buffer = (uint8_t *)dbr;
    fat->curr_sector = -1;
    mutex_init(&(fat->mutex));
    fs->mutex = &(fat->mutex);
    if(fat->table_count != 2 || kernel_memcmp(dbr->BS_FileSysType, "FAT16", 5))
    {
        log_printf("has error.");
        goto mount_failed;
    }

    fs->type = FS_FAT16;
    fs->data = &fs->fat_data;
    fs->dev_id = dev_id;
    return 0;

mount_failed:
    if(dbr) memory_free_page((uint32_t)dbr);
    dev_close(dev_id);
    memory_free_page((uint32_t)fat->fat_buffer);
    return -1;
}     

static void fatfs_unmount(struct _fs_t * fs)
{
    fat_t * fat = &(fs->fat_data);
    dev_close(fs->dev_id);
}

// 从FAT表获取文件表项内容
static int fatfs_open(struct _fs_t * fs, const char * path, file_t * file)
{
    fat_t * fat = (fat_t *)fs->data;
    diritem_t * file_item = 0;
    int file_item_index = -1;

    // 遍历根目录的数据区，找到已经存在的匹配项
    for(int i=0;i < fat->root_ent_count; i++)
    {
        diritem_t *item = read_dir_entry(fat, i);
        if(!item) return -1;

        if(item->DIR_Name[0] == DIRITEM_NAME_FREE)
        {
            file_item_index = i;
            continue;
        } 

        if(item->DIR_Name[0] == DIRITEM_NAME_END)
        {
            file_item_index = i;
            break;
        }   

        if(diritem_name_match(item, path))
        {
            file_item = item;
            file_item_index = i;
            break;
        }
    }

    // 文件存在于FAT表
    if (file_item) {   
        read_from_diritem(fat, file, file_item, file_item_index);
        if(file->mode & O_TRUNC)    // 以空文件的方式打开文件
        {
            cluster_free_chain(fat,file->start_clus);
            file->curr_clus = file->start_clus = FAT_CLUSTER_INVALID;
            file->size = 0;
        }
        return 0;
    }
    else if(file->mode & O_CREAT)
    {
        diritem_t item;
        diritem_init(&item, 0, path);
        int err = write_dir_entry(fat, &item, file_item_index);
        if(err < 0)
        {
            log_printf("create file failed.");
            return -1;
        }

        read_from_diritem(fat, file, &item, file_item_index);
    }
    else
    {
        return -1;
    }

    return 0;
}

static int fatfs_read(char * buff, int size, file_t * file)
{
    fat_t * fat = (fat_t *)file->fs->data;
    uint32_t nbytes = size;
    if(file->pos + nbytes > file->size)
    {
        nbytes = file->size - file->pos;
    }

    uint32_t total_read = 0;        // 已读取字节总数
    // fat文件系统以簇为单位
    while(nbytes > 0)
    {
        uint32_t curr_read = nbytes;    // 这轮读取字节数
        // FAT文件系统簇号从2开始        
        uint32_t offset_cluster = file->pos % fat->bytes_per_clus;  // 文件指针在簇中的位置

        uint32_t start_sector = fat->data_start + (file->curr_clus - 2) * fat->sector_per_clus;
        if(offset_cluster==0 && nbytes==fat->bytes_per_clus)
        {
            int err = dev_read(fat->fs->dev_id, start_sector, buff, fat->sector_per_clus);
            if(err < 0) return total_read;
            curr_read = fat->bytes_per_clus;
        }
        else 
        {
            if(offset_cluster + curr_read > fat->bytes_per_clus)
            {
                curr_read = fat->bytes_per_clus - offset_cluster;
            }

            fat->curr_sector = start_sector;
            int err = dev_read(fat->fs->dev_id, start_sector, fat->fat_buffer, fat->sector_per_clus);
            if(err < 0) return total_read;

            kernel_memcpy(buff, fat->fat_buffer + offset_cluster, curr_read);
        }
        buff += curr_read;
        nbytes -= curr_read;
        total_read += curr_read;

        // 移动当前文件指针
        int err = move_file_pos(file, fat, curr_read, 0);
        if(err < 0) return total_read;
    }  
    return total_read;
}

static int fatfs_write(char * buff, int size, file_t * file)
{
    fat_t * fat = (fat_t *)file->fs->data;

    // 如果文件大小不够，则先扩展文件大小
    if (file->pos + size > file->size) {
        int inc_size = file->pos + size - file->size;
        int err = expand_file(file, inc_size);
        if (err < 0) {
            return 0;
        }
    }

    uint32_t nbytes = size;
    uint32_t total_write = 0;
	while (nbytes) {
        // 每次写的数据量取决于当前簇中剩余的空间，以及size的量综合
        uint32_t curr_write = nbytes;
		uint32_t cluster_offset = file->pos % fat->bytes_per_clus;
        uint32_t start_sector = fat->data_start + (file->curr_clus - 2)* fat->sector_per_clus;  // 从2开始

        // 如果是整簇, 写整簇
        if ((cluster_offset == 0) && (nbytes == fat->bytes_per_clus)) {
            int err = dev_write(fat->fs->dev_id, start_sector, buff, fat->sector_per_clus);
            if (err < 0) {
                return total_write;
            }

            curr_write = fat->bytes_per_clus;
        } else {
            // 如果跨簇，只写第一个簇内的一部分
            if (cluster_offset + curr_write > fat->bytes_per_clus) {
                curr_write = fat->bytes_per_clus - cluster_offset;
            }

            fat->curr_sector = -1;
            int err = dev_read(fat->fs->dev_id, start_sector, fat->fat_buffer, fat->sector_per_clus);
            if (err < 0) {
                return total_write;
            }
            kernel_memcpy(fat->fat_buffer + cluster_offset, buff, curr_write);        
            
            // 写整个簇，然后从中拷贝
            err = dev_write(fat->fs->dev_id, start_sector, fat->fat_buffer, fat->sector_per_clus);
            if (err < 0) {
                return total_write;
            }
        }

        buff += curr_write;
        nbytes -= curr_write;
        total_write += curr_write;

        // 不考虑不截断文件的写入，这样计算文件大小略麻烦
        file->size += curr_write;

        // 前移文件指针
		int err = move_file_pos(file, fat, curr_write, 1);
		if (err < 0) {
            return total_write;
        }
    }
    return total_write;
}

static void fatfs_close(file_t * file)
{
    if(file->mode == O_RDONLY) return;

    fat_t * fat = (fat_t *)file->fs->data;
    diritem_t * item = read_dir_entry(fat, file->file_item_index);
    if(!item) return;

    item->DIR_FileSize = file->size;
    item->DIR_FstClusHI = file->start_clus >> 16;
    item->DIR_FstClusL0 = (uint16_t)(file->start_clus & 0xFFFF);
    write_dir_entry(fat, item, file->file_item_index);
}

//   file.pos = dir + offset.                                            
//  目前只处理shell进程加载,dir==0
static int fatfs_seek(file_t * file, uint32_t offset, int dir)
{
    if(dir != 0) return -1;
    fat_t * fat = file->fs->data;               // 当前的文件系统
    cluster_t curr_clus = file->curr_clus;      // 当前文件所在簇号
    uint32_t curr_file_pos = 0;                      // 当前文件指针
    uint32_t need_offset = offset;              // 当前所需偏移量 

    while(need_offset)
    {
        // 当前文件指针相对于簇开头的偏移
        uint32_t curr_pos_offset = curr_file_pos % fat->bytes_per_clus;
        uint32_t moved = need_offset;       // 实际本次偏移量
        if(curr_pos_offset + moved < fat->bytes_per_clus)
        {
            curr_file_pos += moved;
            break;
        }
        moved = fat->bytes_per_clus - curr_pos_offset;
        curr_file_pos += moved;
        need_offset -= moved;
        curr_clus = cluster_get_next(fat, curr_clus);
        if(!is_cluster_valid(curr_clus)) return -1;
    }
    file->pos = curr_file_pos;
    file->curr_clus = curr_clus;
    return 0;
}

static int fatfs_stat(file_t * file, struct stat * st)
{
    return -1;
}

static int fatfs_opendir(struct _fs_t * fs, const char * name, DIR * dir)
{
    dir->index = 0;
    return 0;
}

static int fatfs_readdir(struct _fs_t * fs, DIR * dir, struct dirent * dirent)
{
    fat_t * fat = (fat_t *)fs->data;
    while (dir->index < fat->root_ent_count)
    {
        diritem_t * item = read_dir_entry(fat, dir->index);
        if(!item) return -1;
        if(item->DIR_Name[0] == DIRITEM_NAME_END) break;
        if(item->DIR_Name[0] != DIRITEM_NAME_FREE)
        {
            file_type_t type = diritem_get_type(item);
            if(type == FILE_NORMAL || type == FILE_DIR)
            {
                // log_printf("dititem index = %d", dir->index);
                dirent->type = type;
                dirent->size = item->DIR_FileSize; 
                diritem_get_name(item, dirent->name);
                dirent->index = dir->index++;
                return 0;
            }
        }
        dir->index++;
    }
    return -1;
}

static int fatfs_closedir(struct _fs_t * fs, DIR * dir)
{
    return 0;
}

static int fatfs_unlink(struct _fs_t * fs, const char * name)
{
    fat_t * fat = (fat_t *)fs->data;
    diritem_t * file_item = 0;
    int file_item_index = -1;

    // 遍历根目录的数据区，找到已经存在的匹配项
    for(int i=0;i < fat->root_ent_count; i++)
    {
        diritem_t *item = read_dir_entry(fat, i);
        if(!item) return -1;

        if(item->DIR_Name[0] == DIRITEM_NAME_FREE)
        {
            file_item_index = i;
            continue;
        } 

        if(item->DIR_Name[0] == DIRITEM_NAME_END)
        {
            file_item_index = i;
            break;
        }   

        if(diritem_name_match(item, name))
        {
            int start_cluster = (item->DIR_FstClusHI << 16) | item->DIR_FstClusL0;
            if(start_cluster != FAT_CLUSTER_INVALID) cluster_free_chain(fat, start_cluster);         // 释放文件簇链

            diritem_t free_item;
            kernel_memset(&free_item, 0, sizeof(diritem_t));
            free_item.DIR_Name[0] = DIRITEM_NAME_FREE;
            free_item.DIR_CrtTime = 1;
            return write_dir_entry(fat, &free_item, i);
        }
    }
    return -1;

}


fs_op_t fatfs_op = {
    .mount = fatfs_mount,
    .unmount= fatfs_unmount,                      // 取消挂载
    .open= fatfs_open,
    .read= fatfs_read,
    .write= fatfs_write,
    .close= fatfs_close,
    .seek= fatfs_seek,
    .stat= fatfs_stat,
    .opendir = fatfs_opendir,
    .closedir = fatfs_closedir,
    .readdir = fatfs_readdir,
    .unlink = fatfs_unlink,
};